//-----------------------------------------------------------------------
// <copyright file="RegistrationPublication.cs" company="iron9light">
//      Copyright (c) iron9light. All rights reserved.
// </copyright>
// <author>iron9light</author>
//-----------------------------------------------------------------------

namespace EventBrokerExtension
{
    using System;
    using System.Globalization;
    using System.Reflection;

    using Microsoft.Practices.ObjectBuilder2;
    using Microsoft.Practices.Unity;

    using Properties;

    public class RegistrationPublication : InjectionMember
    {
        private readonly string eventName;

        private readonly string eventTopic;

        /// <summary>
        /// Initializes a new instance of the <see cref="RegistrationPublication"/> class.
        /// </summary>
        /// <param name="eventName">Name of the event.</param>
        /// <param name="eventTopic">The event topic.</param>
        public RegistrationPublication(string eventName, string eventTopic)
        {
            this.eventName = eventName;
            this.eventTopic = eventTopic;
        }

        /// <summary>
        /// Add policies to the <paramref name="policies"/> to configure the
        /// container to call this constructor with the appropriate parameter values.
        /// </summary>
        /// <param name="typeToCreate">Type to register.</param>
        /// <param name="name">Name used to resolve the type object.</param>
        /// <param name="policies">Policy list to add policies to.</param>
        public override void AddPolicies(Type typeToCreate, string name, IPolicyList policies)
        {
            EventInfo eventInfo = typeToCreate.GetEvent(this.eventName);
            GuardEventExists(eventInfo, typeToCreate, this.eventName);

            GuardEventNotStatic(eventInfo);
            GuardEventHasNoReturn(eventInfo);
            GuardEventHasValidParams(eventInfo);

            SpecifiedPublicationSelectorPolicy selector = GetSelectorPolicy(typeToCreate, name, policies);

            selector.Add(eventInfo, this.eventTopic);
        }

        private static SpecifiedPublicationSelectorPolicy GetSelectorPolicy(Type typeToInject, string name, IPolicyList policies)
        {
            NamedTypeBuildKey key = new NamedTypeBuildKey(typeToInject, name);
            IPublicationSelectorPolicy selector = policies.GetNoDefault<IPublicationSelectorPolicy>(key, false);
            if (selector == null || !(selector is SpecifiedPublicationSelectorPolicy))
            {
                selector = new SpecifiedPublicationSelectorPolicy();
                policies.Set(selector, key);
            }

            return (SpecifiedPublicationSelectorPolicy)selector;
        }

        private static void GuardEventExists(EventInfo eventInfo, Type typeToCreate, string eventName)
        {
            if (eventInfo == null)
            {
                ThrowIllegalRegistrationPublication(Resources.NoSuchEvent, typeToCreate, eventName);
            }
        }

        private static void GuardEventNotStatic(EventInfo eventInfo)
        {
            if (eventInfo.GetAddMethod().IsStatic || eventInfo.GetRemoveMethod().IsStatic)
            {
                ThrowIllegalRegistrationPublication(Resources.CannotRegisterStaticEvent, eventInfo);
            }
        }

        private static void GuardEventHasNoReturn(EventInfo eventInfo)
        {
            MethodInfo invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke");
            if (invokeMethod.ReturnType != typeof(void))
            {
                ThrowIllegalRegistrationPublication(Resources.CannotRegisterEventWithReturn, eventInfo);
            }
        }

        private static void GuardEventHasValidParams(EventInfo eventInfo)
        {
            ParameterInfo[] parameters = GetEventParameters(eventInfo);
            if (parameters.Length == 2)
            {
                if (typeof(object).IsAssignableFrom(parameters[0].ParameterType) && typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType))
                {
                    return;
                }
            }

            ThrowIllegalRegistrationPublication(Resources.CannotRegisterEventWithInvalidParams, eventInfo);
        }

        private static ParameterInfo[] GetEventParameters(EventInfo eventInfo)
        {
            MethodInfo invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke");
            return invokeMethod.GetParameters();
        }

        private static void ThrowIllegalRegistrationPublication(string message, EventInfo eventInfo)
        {
            ThrowIllegalRegistrationPublication(message, eventInfo.DeclaringType, eventInfo.Name);
        }

        private static void ThrowIllegalRegistrationPublication(string message, Type typeToCreate, string eventName)
        {
            throw new InvalidOperationException(
                    string.Format(CultureInfo.CurrentCulture, message, typeToCreate.Name, eventName));
        }
    }
}